package com.stars.easyms.datasource;

import com.stars.easyms.datasource.enums.DataSourceType;
import com.stars.easyms.datasource.enums.DatabaseType;
import com.stars.easyms.datasource.exception.IllegalDataSourceException;
import com.stars.easyms.datasource.holder.EasyMsIllegalDataSourceHolder;
import com.stars.easyms.datasource.holder.EasyMsMasterSlaveDataSourceHolder;
import com.stars.easyms.datasource.holder.EasyMsSynchronizationManager;
import com.stars.easyms.datasource.loadbalancer.LoadBalancer;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;

import javax.sql.DataSource;

/**
 * <p>className: EasyMsMasterSlaveDataSource</p>
 * <p>description: 多主多从数据源</p>
 *
 * @author guoguifang
 * @version 1.1.0
 * @date 2019-02-27 15:01
 */
@Getter
@Slf4j
public final class EasyMsMasterSlaveDataSource extends AbstractRoutingDataSource {

    private DatabaseType databaseType;

    private String dataSourceName;

    private final EasyMsDataSourceSet masterDataSourceSet = new EasyMsDataSourceSet();

    private LoadBalancer masterLoadBalancer;

    private final EasyMsDataSourceSet slaveDataSourceSet = new EasyMsDataSourceSet();

    private LoadBalancer slaveLoadBalancer;

    EasyMsMasterSlaveDataSource(String dataSourceName) {
        this.dataSourceName = dataSourceName;
    }

    @Override
    protected DataSource determineDataSource() {
        int masterDataSourceCount = this.masterDataSourceSet.size();
        int slaveDataSourceCount = this.slaveDataSourceSet.size();
        if (masterDataSourceCount == 0 && slaveDataSourceCount == 0) {
            throw new IllegalDataSourceException("No available datasource '{}' was found!", this.dataSourceName);
        }
        // 在mybatis拦截器层确定使用主数据源还是从数据源，若没有确定的则默认优先使用主数据源
        DataSource dataSource = EasyMsSynchronizationManager.getEasyMsDataSource();
        if (dataSource == null) {
            if (masterDataSourceCount > 0) {
                return getMasterDataSource();
            }
            return getSlaveDataSource();
        }
        return dataSource;
    }

    /**
     * 获取主数据源：如果当前只有一个数据源则直接取该数据源，若没有数据源则抛出异常，若有多个数据源时采用负载均衡策略
     */
    public EasyMsDataSource getMasterDataSource() {
        boolean isFixedDataSource = EasyMsMasterSlaveDataSourceHolder.isFixedDataSource();
        EasyMsDataSource easyMsDataSource = getFixedDataSource(isFixedDataSource, DataSourceType.MASTER);
        if (easyMsDataSource == null) {
            EasyMsDataSourceSet effectiveDataSourceSet = this.masterDataSourceSet.removeAllEasyMsDataSources(EasyMsIllegalDataSourceHolder.get());
            int masterDataSourceCount = effectiveDataSourceSet.size();
            if (masterDataSourceCount == 1) {
                easyMsDataSource = effectiveDataSourceSet.get(0);
            } else if (masterDataSourceCount > 1) {
                easyMsDataSource = masterLoadBalancer.getDataSource(effectiveDataSourceSet);
            } else {
                throw new IllegalDataSourceException("No available master datasource '{}' was found!", this.dataSourceName);
            }
            bindFixedDataSource(isFixedDataSource, DataSourceType.MASTER, easyMsDataSource);
        }
        return easyMsDataSource;
    }

    /**
     * 获取从数据源：如果当前只有一个数据源则直接取该数据源，若没有数据源则返回null，若有多个数据源时采用负载均衡策略
     */
    public EasyMsDataSource getSlaveDataSource() {
        boolean isFixedDataSource = EasyMsMasterSlaveDataSourceHolder.isFixedDataSource();
        EasyMsDataSource easyMsDataSource = getFixedDataSource(isFixedDataSource, DataSourceType.SLAVE);
        if (easyMsDataSource == null) {
            EasyMsDataSourceSet effectiveDataSourceSet = this.slaveDataSourceSet.removeAllEasyMsDataSources(EasyMsIllegalDataSourceHolder.get());
            int slaveDataSourceCount = effectiveDataSourceSet.size();
            if (slaveDataSourceCount == 1) {
                easyMsDataSource = effectiveDataSourceSet.get(0);
            } else if (slaveDataSourceCount > 1) {
                easyMsDataSource = slaveLoadBalancer.getDataSource(effectiveDataSourceSet);
            }
            bindFixedDataSource(isFixedDataSource, DataSourceType.SLAVE, easyMsDataSource);
        }
        return easyMsDataSource;
    }

    void setDatabaseType(DatabaseType databaseType) {
        this.databaseType = databaseType;
    }

    void setMasterLoadBalancer(LoadBalancer masterLoadBalancer) {
        this.masterLoadBalancer = masterLoadBalancer;
    }

    void setSlaveLoadBalancer(LoadBalancer slaveLoadBalancer) {
        this.slaveLoadBalancer = slaveLoadBalancer;
    }

    void addMasterDataSourceSet(EasyMsDataSourceSet masterDataSourceSet) {
        if (!CollectionUtils.isEmpty(masterDataSourceSet)) {
            this.masterDataSourceSet.addAllEasyMsDataSources(masterDataSourceSet);
        }
    }

    void addSlaveDataSourceSet(EasyMsDataSourceSet slaveDataSourceSet) {
        if (!CollectionUtils.isEmpty(slaveDataSourceSet)) {
            this.slaveDataSourceSet.addAllEasyMsDataSources(slaveDataSourceSet);
        }
    }

    private EasyMsDataSource getFixedDataSource(boolean isFixedDataSource, @NonNull DataSourceType dataSourceType) {
        EasyMsDataSource easyMsDataSource = null;
        if (isFixedDataSource) {
            easyMsDataSource = EasyMsMasterSlaveDataSourceHolder.getFixedDataSource(this, dataSourceType);
        }
        return easyMsDataSource;
    }

    private void bindFixedDataSource(boolean isFixedDataSource, @NonNull DataSourceType dataSourceType, @Nullable EasyMsDataSource easyMsDataSource) {
        // 如果是固定数据源的话则进行绑定
        if (isFixedDataSource && easyMsDataSource != null) {
            EasyMsMasterSlaveDataSourceHolder.bindFixedDataSource(this, dataSourceType, easyMsDataSource);
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || obj.getClass() != this.getClass()) {
            return false;
        }
        EasyMsMasterSlaveDataSource easyMsMasterSlaveDataSource = (EasyMsMasterSlaveDataSource) obj;
        return this.dataSourceName.equals(easyMsMasterSlaveDataSource.dataSourceName);
    }

    @Override
    public int hashCode() {
        return 59 + this.dataSourceName.hashCode();
    }

}